package file

import (
	"clouddisk/internal/common"
	"clouddisk/model"
	pb "clouddisk/proto/file"
	"context"
	"fmt"
	"log"
	"net"
	"net/url"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"
	"time"

	"google.golang.org/grpc"
	"gorm.io/gorm"
)

type FileServiceImpl struct {
	pb.FileServiceServer
	registry   *common.Registry
	store_path string
	mysql      *gorm.DB
}

func (fs *FileServiceImpl) PutSingleFile(ctx context.Context, req *pb.PutSingleFileReq) (rsp *pb.PutSingleFileRsp, err error) {
	rsp = new(pb.PutSingleFileRsp)
	rsp.RequestId = req.RequestId

	path := filepath.Join(req.UserId, req.FilePath, req.FileName)
	var exist bool
	err = fs.mysql.Model(&model.FileInfo{}).Select("COUNT(*) > 0").Where("path = ?", path).Find(&exist).Error
	if err != nil {
		rsp.Code = 1
		return rsp, fmt.Errorf("数据库查询错误 %w", err)
	}
	if exist {
		ext := filepath.Ext(path)
		base := path[:len(path)-len(ext)]
		timestamp := time.Now().Format("20060102150405")
		path = base + "_" + timestamp + ext
	}

	// 分配fid
	fid := common.GenerateRandomString(8)

	// 对应属性写入文件表
	fmsg := &model.FileInfo{
		Fid:      fid,
		Path:     path,
		Uid:      req.UserId,
		Size:     len(req.FileContent),
		Lasttime: time.Now(),
	}

	ret := fs.mysql.Create(fmsg)
	if ret.Error != nil {
		rsp.Code = 1
		return rsp, fmt.Errorf("插入数据失败: %w", ret.Error)
	}

	// 保存至本地
	storage := filepath.Join(fs.store_path, fid)
	err = common.FileStore(storage, req.FileContent)
	if err != nil {
		rsp.Code = 1
		return rsp, err
	}

	rsp.Code = 2
	return rsp, nil
}

func (fs *FileServiceImpl) GetSingleFile(ctx context.Context, req *pb.GetSingleFileReq) (rsp *pb.GetSingleFileRsp, err error) {
	rsp = new(pb.GetSingleFileRsp)
	rsp.RequestId = req.RequestId
	// 获取文件信息
	var fd model.FileInfo
	err = fs.mysql.Model(&model.FileInfo{}).Select("*").Where("path = ?", req.FilePath).First(&fd).Error
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			rsp.Code = 1
			rsp.Errmsg = "没有找到对应文件信息"
			return rsp, nil
		} else {
			rsp.Code = 1
			return rsp, fmt.Errorf("数据库查询错误 %w", err)
		}
	}

	// 判断鉴权
	if fd.Uid != req.UserId {
		rsp.Code = 1
		rsp.Errmsg = "非本用户所属文件,无法获取"
		return rsp, nil
	}

	// // 更新atime
	// ret := fs.mysql.Model(&fd).Where("id = ?", fd.ID).Update("lasttime", time.Now())
	// if ret.Error != nil {
	// 	rsp.Code = 1
	// 	return rsp, fmt.Errorf("数据库查询错误 %w", ret.Error)
	// }

	// 清除缓存

	// 找到对应文件返回
	path := filepath.Join(fs.store_path, fd.Fid)
	rsp.FileContent, err = common.FileRead(path)
	if err != nil {
		rsp.Code = 1
		return rsp, fmt.Errorf("读取本地文件失败 %w", err)
	}

	pos := strings.LastIndex(req.FilePath, "/")

	rsp.FileName = req.FilePath[pos+1:]
	rsp.Size = common.ByteSize(fd.Size)
	rsp.Code = 2
	return rsp, nil
}

func (fs *FileServiceImpl) GetUserFileList(ctx context.Context, req *pb.GetUserFileListReq) (rsp *pb.GetUserFileListRsp, err error) {
	rsp = new(pb.GetUserFileListRsp)
	rsp.RequestId = req.RequestId
	rsp.Offset = req.Offset
	rsp.PageSize = req.PageSize

	if req.PageSize <= 0 || req.PageSize > 50 {
		rsp.Code = 1
		rsp.Errmsg = "非法的页面长度"
		return rsp, nil
	}
	path := filepath.Join(req.UserId, req.ScanDir)

	// redis缓存优化

	// 匹配对应目录下的文件
	var fds []model.FileInfo
	offset := req.Offset * req.PageSize
	ret := fs.mysql.Model(&model.FileInfo{}).Select("*").
		Where("path like ?", path+"%").Offset(int(offset)).
		Limit(int(req.PageSize)).Find(&fds)
	if ret.Error != nil {
		if err == gorm.ErrRecordNotFound {
			rsp.Code = 2
			rsp.TotalCnt = 0
			return rsp, nil
		} else {
			rsp.Code = 1
			return rsp, fmt.Errorf("数据库查询错误 %w", err)
		}
	}

	var total int64
	ret = fs.mysql.Model(&model.FileInfo{}).Where("path like ?", path+"%").Count(&total)
	if ret.Error != nil {
		rsp.Code = 1
		return rsp, fmt.Errorf("数据库查询错误 %w", err)
	}
	rsp.TotalCnt = (int32(total) + req.PageSize - 1) / req.PageSize

	// 整理返回文件信息
	for _, fd := range fds {
		pos := strings.LastIndex(fd.Path, "/")
		name := fd.Path[pos+1:]
		size := common.ByteSize(fd.Size)
		durl, err := url.JoinPath("/download", fd.Path)
		if err != nil {
			log.Printf("%v 拼接下载连接失败: %v", fd.Path, err)
			continue
		}
		rsp.Files = append(rsp.Files, &pb.FileDetail{
			FileName:    name,
			Size:        size,
			Downloadurl: durl,
			LastTime:    fd.Lasttime.Format("2006-01-02 15:04:05"),
		})
	}

	rsp.Code = 2
	return rsp, nil
}

func (fs *FileServiceImpl) FileRename(ctx context.Context, req *pb.FileRenameReq) (rsp *pb.FileRenameRsp, err error) {
	rsp = new(pb.FileRenameRsp)
	rsp.RequestId = req.RequestId

	// 判断旧文件存在
	path := filepath.Join(req.UserId, req.FilePath, req.FileName)
	var exist bool
	err = fs.mysql.Model(&model.FileInfo{}).Select("COUNT(*) > 0").Where("path = ?", path).Find(&exist).Error
	if err != nil {
		rsp.Code = 1
		return rsp, fmt.Errorf("数据库查询错误 %w", err)
	}

	if !exist {
		rsp.Code = 1
		rsp.Errmsg = "该文件不存在"
		return rsp, nil
	}

	// 判断新文件名存在
	npath := filepath.Join(req.UserId, req.FilePath, req.NewName)
	err = fs.mysql.Model(&model.FileInfo{}).Select("COUNT(*) > 0").Where("path = ?", npath).Find(&exist).Error
	if err != nil {
		rsp.Code = 1
		return rsp, fmt.Errorf("数据库查询错误 %w", err)
	}

	if exist {
		rsp.Code = 1
		rsp.Errmsg = "新文件名存在,请重试"
		return rsp, nil
	}

	// 写入新文件名
	err = fs.mysql.Model(&model.FileInfo{}).Where("path = ?", path).Update("path", npath).Error
	if err != nil {
		rsp.Code = 1
		return rsp, fmt.Errorf("数据库更新失败 %w", err)
	}

	// 组织响应
	rsp.Code = 2
	return rsp, nil
}

func (fs *FileServiceImpl) FileDelete(ctx context.Context, req *pb.FileDelteReq) (rsp *pb.FileDelteRsp, err error) {
	rsp = new(pb.FileDelteRsp)
	rsp.RequestId = req.RequestId

	// 判断旧文件存在
	path := filepath.Join(req.UserId, req.FilePath, req.FileName)
	log.Println(path)
	var files []model.FileInfo
	err = fs.mysql.Model(&model.FileInfo{}).Select("*").Where("path = ?", path).Find(&files).Error
	if err != nil {
		rsp.Code = 1
		return rsp, fmt.Errorf("数据库查询错误 %w", err)
	}

	if len(files) < 1 {
		rsp.Code = 1
		rsp.Errmsg = "该文件不存在"
		return rsp, nil
	}

	file := files[0]

	// 删除文件数据
	store := filepath.Join(fs.store_path, file.Fid)
	err = os.Remove(store)
	if err != nil {
		rsp.Code = 1
		rsp.Errmsg = "删除文件失败"
		return rsp, nil
	}

	// 清除文件消息
	err = fs.mysql.Model(&model.FileInfo{}).Delete(&file).Error
	if err != nil {
		rsp.Code = 1
		rsp.Errmsg = "删除文件数据失败"
		return rsp, nil
	}

	// 组织响应
	rsp.Code = 2
	return rsp, nil
}

type FileServer struct {
	rpcServer *grpc.Server
	listener  net.Listener
	registry  *common.Registry
	mysql     *gorm.DB
}

func (fs *FileServer) Serve() error {
	stopChan := make(chan os.Signal, 1)
	signal.Notify(stopChan, os.Interrupt, syscall.SIGTERM)

	go func() {
		<-stopChan
		log.Println("程序开始平滑退出")
		fs.rpcServer.GracefulStop()
	}()

	if err := fs.rpcServer.Serve(fs.listener); err != nil {
		return fmt.Errorf("failed to serve: %v", err)
	}

	return nil
}

type FileServiceBuilder struct {
	rpcServer *grpc.Server
	db        *gorm.DB
	registry  *common.Registry
	listener  net.Listener
	err       error
}

func NewFileServerBuilder() *FileServiceBuilder {
	return &FileServiceBuilder{}
}

func (fb *FileServiceBuilder) WithMysqlObject(dsn string) *FileServiceBuilder {
	if fb.err != nil {
		return fb
	}
	fb.db, fb.err = common.ConnectMysql(dsn)
	if fb.err != nil {
		return fb
	}
	fb.err = fb.db.AutoMigrate(&model.FileInfo{}) // 自动创建表
	return fb
}

func (fb *FileServiceBuilder) WithRegisterObject(host, ServiceName, AccessHost string) *FileServiceBuilder {
	if fb.err != nil {
		return fb
	}
	fb.registry, fb.err = common.NewRegistry(host)
	if fb.err != nil {
		return fb
	}
	fb.err = fb.registry.Register(ServiceName, AccessHost)
	return fb
}

func (fb *FileServiceBuilder) WithRPCObject(port, path string) *FileServiceBuilder {
	if fb.err != nil {
		return fb
	}

	if fb.registry == nil || fb.db == nil {
		fb.err = fmt.Errorf("必须组件未完成初始化")
		return fb
	}

	fb.listener, fb.err = net.Listen("tcp", ":"+port)
	fb.rpcServer = grpc.NewServer()
	pb.RegisterFileServiceServer(fb.rpcServer, &FileServiceImpl{
		registry:   fb.registry,
		mysql:      fb.db,
		store_path: path,
	})

	return fb
}

func (fb *FileServiceBuilder) Build() (*FileServer, error) {
	if fb.err != nil {
		return nil, fb.err
	}

	if fb.rpcServer == nil {
		fb.err = fmt.Errorf("rpcserver未完成初始化")
		return nil, fb.err
	}

	return &FileServer{
		rpcServer: fb.rpcServer,
		listener:  fb.listener,
	}, nil
}
